//
//  TCPServer.m
//  NXTBluetoothBridge
//
//  Created by Daniel Siemer on 4/4/09.
/*
 The MIT License
 
 Copyright (c) 2009 Daniel Siemer
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 */

#import "TCPServer.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

NSString * const TCPServerErrorDomain = @"TCPServerErrorDomain";

@implementation TCPServer
@synthesize delegate;
@synthesize domain;
@synthesize name;
@synthesize type;
@synthesize port;
@synthesize ipv4socket;
@synthesize ipv6socket;
@synthesize netService;

- (id)init {
   return self;
}

- (void)dealloc {
   [self stop];
   [domain release];
   [name release];
   [type release];
   [super dealloc];
}

- (void)handleNewConnectionFromAddress:(NSData *)addr inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr {
   // if the delegate implements the delegate method, call it  
   if (delegate && [delegate respondsToSelector:@selector(TCPServer:didReceiveConnectionFrom:inputStream:outputStream:)]) { 
      [delegate TCPServer:self didReceiveConnectionFromAddress:addr inputStream:istr outputStream:ostr];
   }
}

static void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
   TCPServer *server = (TCPServer *)info;
   if (kCFSocketAcceptCallBack == type) { 
      // for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle
      CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
      uint8_t name[SOCK_MAXADDRLEN];
      socklen_t namelen = sizeof(name);
      NSData *peer = nil;
      if (0 == getpeername(nativeSocketHandle, (struct sockaddr *)name, &namelen)) {
         peer = [NSData dataWithBytes:name length:namelen];
      }
      CFReadStreamRef readStream = NULL;
      CFWriteStreamRef writeStream = NULL;
      CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &readStream, &writeStream);
      if (readStream && writeStream) {
         CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
         CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
         [server handleNewConnectionFromAddress:peer inputStream:(NSInputStream *)readStream outputStream:(NSOutputStream *)writeStream];
      } else {
         // on any failure, need to destroy the CFSocketNativeHandle 
         // since we are not going to use it any more
         close(nativeSocketHandle);
      }
      if (readStream) CFRelease(readStream);
      if (writeStream) CFRelease(writeStream);
   }
}

- (BOOL)start:(NSError **)error {
   CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
   ipv4socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt);
   ipv6socket = CFSocketCreate(kCFAllocatorDefault, PF_INET6, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt);
   
   if (NULL == ipv4socket || NULL == ipv6socket) {
      if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerNoSocketsAvailable userInfo:nil];
      if (ipv4socket) CFRelease(ipv4socket);
      if (ipv6socket) CFRelease(ipv6socket);
      ipv4socket = NULL;
      ipv6socket = NULL;
      return NO;
   }
   
   int yes = 1;
   setsockopt(CFSocketGetNative(ipv4socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
   setsockopt(CFSocketGetNative(ipv6socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
   
   // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
   struct sockaddr_in addr4;
   memset(&addr4, 0, sizeof(addr4));
   addr4.sin_len = sizeof(addr4);
   addr4.sin_family = AF_INET;
   addr4.sin_port = htons(port);
   addr4.sin_addr.s_addr = htonl(INADDR_ANY);
   NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];
   
   if (kCFSocketSuccess != CFSocketSetAddress(ipv4socket, (CFDataRef)address4)) {
      if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv4Address userInfo:nil];
      if (ipv4socket) CFRelease(ipv4socket);
      if (ipv6socket) CFRelease(ipv6socket);
      ipv4socket = NULL;
      ipv6socket = NULL;
      return NO;
   }
   
   if (0 == port) {
      // now that the binding was successful, we get the port number 
      // -- we will need it for the v6 endpoint and for the NSNetService
      NSData *addr = [(NSData *)CFSocketCopyAddress(ipv4socket) autorelease];
      memcpy(&addr4, [addr bytes], [addr length]);
      port = ntohs(addr4.sin_port);
   }
   
   // set up the IPv6 endpoint
   struct sockaddr_in6 addr6;
   memset(&addr6, 0, sizeof(addr6));
   addr6.sin6_len = sizeof(addr6);
   addr6.sin6_family = AF_INET6;
   addr6.sin6_port = htons(port);
   memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
   NSData *address6 = [NSData dataWithBytes:&addr6 length:sizeof(addr6)];
   
   if (kCFSocketSuccess != CFSocketSetAddress(ipv6socket, (CFDataRef)address6)) {
      if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv6Address userInfo:nil];
      if (ipv4socket) CFRelease(ipv4socket);
      if (ipv6socket) CFRelease(ipv6socket);
      ipv4socket = NULL;
      ipv6socket = NULL;
      return NO;
   }
   
   // set up the run loop sources for the sockets
   CFRunLoopRef cfrl = CFRunLoopGetCurrent();
   CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ipv4socket, 0);
   CFRunLoopAddSource(cfrl, source4, kCFRunLoopCommonModes);
   CFRelease(source4);
   
   CFRunLoopSourceRef source6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ipv6socket, 0);
   CFRunLoopAddSource(cfrl, source6, kCFRunLoopCommonModes);
   CFRelease(source6);
   
   // we can only publish the service if we have a type to publish with
   if (nil != type) {
      NSString *publishingDomain = domain ? domain : @"";
      NSString *publishingName = nil;
      if (nil != name) {
         publishingName = name;
      } else {
         NSString * thisHostName = [[NSProcessInfo processInfo] hostName];
         if ([thisHostName hasSuffix:@".local"]) {
            publishingName = [thisHostName substringToIndex:([thisHostName length] - 6)];
         }
      }
      netService = [[NSNetService alloc] initWithDomain:publishingDomain type:type name:publishingName port:port];
      [netService publish];
   }
   
   return YES;
}

- (BOOL)stop {
   [netService stop];
   [netService release];
   netService = nil;
   CFSocketInvalidate(ipv4socket);
   CFSocketInvalidate(ipv6socket);
   CFRelease(ipv4socket);
   CFRelease(ipv6socket);
   ipv4socket = NULL;
   ipv6socket = NULL;
   return YES;
}

@end
